package com.health.trans.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.health.trans.config.dataSource.AbstractBsDts;
import com.health.trans.pojo.JpaHelper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.annotation.DependsOn;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author nqlz
 */
@Slf4j
public class DataSourceUtil {

    public static Map<String, AbstractBsDts> DS_MAP;
    public static Map<String, List<String>> tablesMap;
    private static final Map<String, JpaHelper> JPA_HELPER_MAP = new HashMap<>();
    public static List<DbRs> dbRsList = new ArrayList<>();

    public DataSourceUtil(List<AbstractBsDts> bsDts) {
        DS_MAP = bsDts.stream()
                .peek(i -> JPA_HELPER_MAP.put(i.getDataSourceName(), new JpaHelper(i.getEntityManager())))
                .collect(Collectors.toMap(AbstractBsDts::getDataSourceName, a -> a));
        tablesMap = bsDts.stream().collect(Collectors.toMap(AbstractBsDts::getDataSourceName, a -> getAllTableNames(a.getDataSourceName())));

        ThreadUtil.execAsync(this::initDbrs);
    }

    private void initDbrs() {
        tablesMap.forEach((dataSource, v) -> {
            DbRs dbRs = new DbRs();
            dbRs.setDataSource(dataSource);
            JpaHelper jpaHelper = getJpaHelper(dataSource);
            List<TableRs> tableRsList = v.stream().map(table -> {
                TableRs tableRs = new TableRs();
                tableRs.setTableName(table);
                List<Map<Object, Object>> maps = jpaHelper.searchMaps("DESCRIBE " + table);
                List<String> field = maps.stream().map(i -> i.get("Field").toString()).collect(Collectors.toList());
                tableRs.setFields(field);
                return tableRs;
            }).collect(Collectors.toList());
            dbRs.setTableRsList(tableRsList);
            dbRsList.add(dbRs);
        });
    }

    public static DataSource getDataSource(String name) {
        return DS_MAP.get(name).getDataSource();
    }

    public static boolean isExist(String... names) {
        return DS_MAP.keySet().containsAll(Arrays.asList(names));
    }

    /**
     * 得到实体管理器工厂
     *
     * @param sourceName 源名称
     * @return {@link EntityManagerFactory}
     */
    public static EntityManagerFactory getEntityManagerFactory(String sourceName) {
        if (!DS_MAP.containsKey(sourceName)) {
            log.error("{}该数据源不存在，请检查。", sourceName);
            ExceptionUtil.wrapRuntimeAndThrow(sourceName + "该数据源不存在，请检查。");
        }
        return DS_MAP.get(sourceName).getEntityManagerFactory().getObject();
    }

    /**
     * 获取事务管理器
     *
     * @param sourceName 源名称
     * @return {@link PlatformTransactionManager}
     */
    public static PlatformTransactionManager getTransactionManager(String sourceName) {
        if (!DS_MAP.containsKey(sourceName)) {
            ExceptionUtil.wrapRuntimeAndThrow(sourceName + "数据源不存在");
        }
        return DS_MAP.get(sourceName).getTransactionManager();
    }

    /**
     * 得到实体管理器
     *
     * @param sourceName 源名称
     * @return {@link EntityManager}
     */
    public static EntityManager getEntityManager(String sourceName) {
        if (!DS_MAP.containsKey(sourceName)) {
            ExceptionUtil.wrapRuntimeAndThrow(sourceName + "数据源不存在");
        }
        return DS_MAP.get(sourceName).getEntityManager();
    }

    /**
     * 获取jpa助手
     *
     * @param sourceName 源名称
     * @return {@link JpaHelper}
     */
    public static JpaHelper getJpaHelper(String sourceName) {
        if (!JPA_HELPER_MAP.containsKey(sourceName)) {
            ExceptionUtil.wrapRuntimeAndThrow(sourceName + "数据源不存在");
        }
        return JPA_HELPER_MAP.get(sourceName);
    }

    /**
     * 得到所有表名
     *
     * @param dataSource 数据源
     * @return {@link List}<{@link String}>
     */
    public List<String> getAllTableNames(String dataSource) {
        List<String> list = new ArrayList<>();
        SessionFactory sessionFactory = getEntityManagerFactory(dataSource).unwrap(SessionFactory.class);
        Session session = sessionFactory.openSession();
        if (session != null) {
            list = session.doReturningWork(
                    connection -> {
                        List<String> resultList = new ArrayList<>();
                        ResultSet rs;
                        String username = connection.getMetaData().getUserName();
                        try {
                            rs = connection.getMetaData().getTables(connection.getCatalog(), username, null, null);
                            while (rs.next()) {
                                String table = rs.getObject(3).toString();
                                resultList.add(table);
                            }
                        } catch (SQLException e) {
                            //不存在
                            return resultList;
                        }
                        rs.close();
                        return resultList;
                    }
            );
            session.close();
        }
        return list;
    }


    /**
     * 得到所有列名
     *
     * @param dataSource 数据源
     * @param tableName  表名
     * @param catalog    目录
     * @return {@link List}<{@link String}>
     */
    public static List<String> getAllColumnNames(String dataSource, @Nullable String catalog, String tableName) {
        List<String> list = new ArrayList<>();
        SessionFactory sessionFactory = getEntityManagerFactory(dataSource).unwrap(SessionFactory.class);
        Session session = sessionFactory.openSession();
        if (session != null) {
            list = session.doReturningWork(
                    connection -> {
                        List<String> resultList = new ArrayList<>();
                        ResultSet rs;
                        String username = connection.getMetaData().getUserName();
                        try {
                            rs = connection.getMetaData().getColumns(StrUtil.isBlank(catalog) ? connection.getCatalog() : catalog, username, tableName, null);
                            while (rs.next()) {
                                String column = rs.getObject(4).toString();
                                resultList.add(column);
                            }
                        } catch (SQLException e) {
                            //不存在
                            return resultList;
                        }
                        rs.close();
                        return resultList;
                    }
            );
            session.close();
        }
        return list;
    }

    public static boolean checkColumnNameExist(String dataSource, String catalog, String tableName, String columnName) {
        List<String> columnNameList = getAllColumnNames(dataSource, catalog, tableName);
        return CollUtil.isNotEmpty(columnNameList) && columnNameList.contains(columnName);
    }

    public static boolean checkColumnNameExist(String dataSource, String catalog, String tableName, List<String> columnNames) {
        List<String> columnNameList = getAllColumnNames(dataSource, catalog, tableName);
        return CollUtil.isNotEmpty(columnNameList) && CollUtil.containsAll(columnNameList, columnNames);
    }

    public static List<?> getFieldsByTableName(String dsName, String tableName) {
        JpaHelper jpaHelper = getJpaHelper(dsName);
        List<Map<Object, Object>> maps = jpaHelper.searchMaps("DESCRIBE " + tableName);
        List<String> field = maps.stream().map(i -> i.get("Field").toString()).collect(Collectors.toList());
        return field;
    }

    @Data
    static class DbRs {
        private String dataSource;
        private List<TableRs> tableRsList;
    }

    @Data
    static class TableRs {
        private String tableName;
        private List<String> fields;
    }

}
